unit frmMainUnit;
{$POINTERMATH ON}

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, Math, VCLTee.TeEngine, Vcl.ExtCtrls, VCLTee.TeeProcs,
  VCLTee.Chart, VCLTee.Series, VCLTee.BubbleCh, Vcl.ComCtrls, NeuroNets,
  VCLTee.TeeSpline, Xml.XMLIntf, Xml.XMLDoc;

type
  TForm2 = class(TForm)
    Button1: TButton;
    Chart1: TChart;
    Series1: TBubbleSeries;
    UpDown1: TUpDown;
    Chart2: TChart;
    Series2: TBarSeries;
    Timer1: TTimer;
    Label1: TLabel;
    UpDown2: TUpDown;
    Label2: TLabel;
    UpDown3: TUpDown;
    UpDown4: TUpDown;
    Label3: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FWMul, FWShift: Double;
    FBMul, FBShift: Double;
    FIMul, FIShift: Double;

    procedure UpdateMulShift;
    procedure CreateNet(AHiddenLayers: Integer; out ANet: TNeuroNet);
    function  Run: TArray<Double>;
    procedure Visualize(const AData: TArray<Double>);
  end;

const
  HIDDEN_LAYERS = 1;
  LAYER_NODES   = 300;
  RUN_COUNT     = 3000 div LAYER_NODES;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.CreateNet(AHiddenLayers: Integer; out ANet: TNeuroNet);
var
  prevL, curL: array[0..LAYER_NODES - 1] of TNetBuilder.PNode;
  i:           Integer;
  b:           TNetBuilder;

  function _RandomWeight: Double;
  begin
    Result := FWMul * Random + FWShift;
  end;

  function _RandomBias: Double;
  begin
    Result := FBMul * Random + FBShift;
  end;

  function _RandomInput: Double;
  begin
    Result := FIMul * Random + FIShift;
  end;

  procedure _Connect;
  var
    i, j: Integer;
  begin
    for i := 0 to LAYER_NODES - 1 do
      for j := 0 to LAYER_NODES - 1 do
        b.AddConnection(prevL[j], curL[i], _RandomWeight);
  end;

begin
  b := TNetBuilder.Create;
  try
    for i := 0 to LAYER_NODES - 1 do
      curL[i] := b.AddInput;

    while AHiddenLayers > 0 do
    begin
      prevL := curL;
      for i := 0 to LAYER_NODES - 1 do
        curL[i] := b.AddNode(_RandomBias);
      _Connect;

      Dec(AHiddenLayers);
    end;

    prevL := curL;
    for i := 0 to LAYER_NODES - 1 do
      curL[i] := b.AddOutput(_RandomBias);
    _Connect;

    b.Compile(ANet);
    for i := 0 to LAYER_NODES - 1 do
      ANet.Inputs[i] := _RandomInput;
  finally
    b.Free;
  end;
end;

function TForm2.Run: TArray<Double>;
var
  net: TNeuroNet;
  i:   Integer;
  j: Integer;
begin
  SetLength(Result, RUN_COUNT * LAYER_NODES);

  for i := 0 to RUN_COUNT - 1 do
  begin
    net := Default(TNeuroNet);
    CreateNet(HIDDEN_LAYERS, net);

    net.Execute();
    for j := 0 to LAYER_NODES - 1 do
      Result[i * LAYER_NODES + j] := net.Outputs^[j];

    net.DisposeAll;
  end;
end;

procedure TForm2.Visualize(const AData: TArray<Double>);
var
  i:    Integer;
  freq: array[0..20] of Integer;
  n:    Integer;
begin
  Series1.BeginUpdate;
  Series2.BeginUpdate;
  try
    Series1.Clear;
    Series2.Clear;

    for i := 0 to High(freq) do
      freq[i] := 0;

    for i := 0 to High(AData) do
    begin
      Series1.AddBubble(i, AData[i], 0.01);

      n := Max(0, Min(High(freq), Trunc(((AData[i] + 1) / 2) * Length(freq))));
      Inc(freq[n]);
    end;

    for i := 0 to High(freq) do
      Series2.AddXY(i, freq[i]);
  finally
    Series1.EndUpdate;
    Series2.EndUpdate;
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  xml: IXMLDocument;
begin
  xml := LoadXMLDocument('Settings.xml');

  UpDown1.Position := xml.DocumentElement.ChildValues['WPos'];
  UpDown2.Position := xml.DocumentElement.ChildValues['BPos'];
  UpDown3.Position := xml.DocumentElement.ChildValues['IPosA'];
  UpDown4.Position := xml.DocumentElement.ChildValues['IPosB'];

  UpdateMulShift;
end;

procedure TForm2.FormDestroy(Sender: TObject);
var
  xml: IXMLDocument;
begin
  xml                 := NewXMLDocument;
  xml.Options         := xml.Options + [doNodeAutoIndent];
  xml.DocumentElement := xml.CreateElement('asdsa', '');

  xml.DocumentElement.ChildValues['WPos']  := UpDown1.Position;
  xml.DocumentElement.ChildValues['BPos']  := UpDown2.Position;
  xml.DocumentElement.ChildValues['IPosA'] := UpDown3.Position;
  xml.DocumentElement.ChildValues['IPosB'] := UpDown4.Position;

  xml.SaveToFile('Settings.xml');
end;

procedure TForm2.Button1Click(Sender: TObject);
var
  res: TArray<Double>;
begin
  res := Run;
  Visualize(res);
end;

procedure TForm2.UpdateMulShift;
begin
  FWMul   := (UpDown1.Position / 10);
  FWShift := (-FWMul / 2);

  FBMul   := (UpDown2.Position / 10);
  FBShift := (-FBMul / 2);

  FIMul   := (UpDown3.Position / 10);
  FIShift := (-FIMul / 2) + (UpDown4.Position / 10);

  Label1.Caption := 'Weight range = [' + FloatToStr(FWShift) + '..' +
                    FloatToStr(FWMul + FWShift) + ']';

  Label2.Caption := 'Bias range = [' + FloatToStr(FBShift) + '..' +
                    FloatToStr(FBMul + FBShift) + ']';

  Label3.Caption := 'Input range = [' + FloatToStr(FIShift) + '..' +
                    FloatToStr(FIMul + FIShift) + ']';
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  UpdateMulShift;
  Button1Click(Button1);
end;

end.
